defmodule Neo4jLib do
	@moduledoc """
	    1.暂时不支持复合主键的情况
	    2.支持根据不同的对象在 对象与对象 的关系上设置参数的情况
	        [%{}]， 列表里的键包含:对象的主键，关系对象的字段
	    3.支持关系上没有参数的情况
	    4.支持关系上有参数的情况
	"""

	import Util
	import SyntaxMacro
	import ErrCode
	@struct_field :__struct__


	@doc """
	belong_uid: 项目id
	struct: key可以是待插入对象的属性,也可以是关系对象,也可以是关系上的对象
	假设 lab 有属性, id, name
	lab 有多个dutyer(值日人), (dutyer)-[:DUTY{week_day: day}]->(lab)
	lab 有 area 区域, (lab)-[:AT]->(area)
	设置值日人: %{"dutyer"=> [%{"人唯一id字段"=> id, "week_day" => day}}
	    unwind list as row
	    optional match (p)<-[:ACCESS]-(dutyer{id: row.id}) where dutyer.id in ids
	    merge (dutyer)-[duty:DUTY{week_day: row.day}]->(lab)
	设置所属区域: %{"area" => area的唯一id字段}
	    optional match (p)<-[:BELONG]-(area) where area.id in ids
	    merge (lab)-[:AT]->(area)
	设置lab属性: %{"id" => id, "name" => name}
	    set lab.id = id, lab.name = name
	新增lab及其关系: %{"id" => id, "name" => name, "dutyer" => dutyer, "area" => area}
	"""
	def insert(belong_uid, struct) do
		write(get_function(), belong_uid, nil, struct)
	end

	@doc """
	struct: key可以是待插入对象的属性,也可以是关系对象,也可以是关系上的对象
	设置值日人: %{"dutyer"=> [%{"人唯一id字段"=> id, "week_day" => day}},会拿到该 lab 的所有值日人,没有当前设置id的人会被删除
	    也即: optional match (p)<-[:ACCESS]-(dutyer)-[duty1:DUTY]->(lab) where dutyer.id not in ids delete duty1
	设置所属区域: %{"area" => area的唯一id字段}
	    optional match (p)<-[:BELONG]-(area)<-[at1:AT]-(lab) delete at1
	设置lab属性: %{"id" => id, "name" => name}
	    set lab.id = id, lab.name = name
	"""
	def update(belong_uid, unique_key, struct) do
		write(get_function(), belong_uid, unique_key, struct)
	end


	@doc """
	match:[]
	optional: []
	select: %{"obj1" => ["字段1"...], "obj2" => ["字段2"]}
	where:
	  %{"字段1" => "值", "or" => %{}, "IS" => %{}, "like" => %{}, ">" => %{}}
	      字段1: 对象+字段; ex: technique.name, contains.step
	      值: list, string, integer, float...
	"""
	@spec get(struct | charlist, atom, list, list, map | list, map) :: charlist
	def get(uid_or_user, mod, match, optional, select, where) do
		Pango.DeeprCypherV2.get(uid_or_user, mod, match, optional, select, where)
	end

	def create_rel(belong_id, unique_key, struct) when is_map(struct) do
		write(get_function(), belong_id, unique_key, struct)
	end

	def delete_rel(belong_uid, unique_key, struct) do
		check_params_valid!(unique_key)
		{target_mod, struct} = Map.pop(struct, @struct_field)
		check_params_valid!(struct)
		{return, struct} = Map.pop(struct, :returning, %{})
		{with1, struct} = Map.pop(struct, :with, [])
		{filter, struct} = Map.pop(struct, :filter, [])
		{primary_key, _} = target_mod.primary_key()
		%{:dir => belong_dir, :mod => belong_mod, :label => belong_label} = target_mod.belong_to()
		target = target_mod.deepr_object()
		belong_obj = belong_mod.deepr_object()
		all_fields = target_mod.fields()
		get_belong = Pango.DeeprCypherV2.get_belong_str(belong_uid, target_mod, target, %{})
		<> " WHERE #{target}.#{primary_key} = #{inspect(unique_key)}"
		{get_acc, with_acc, del_acc} = Enum.reduce(
			struct,
			{[], [], []},
			fn ({rel_obj, rel_unique_key}, {get_acc, with_acc, del_acc}) ->
				case Map.get(all_fields, rel_obj) do
					nil ->
						PrivateCandy.remind(__ENV__)
					%{dir: rel_dir, mod: rel_mod, label: rel_label} ->
						{primary_rel_key, _} = rel_mod.primary_key()
						key = if is_list(rel_unique_key) do
							" IN #{inspect(rel_unique_key)}"
						else
							" = " <> inspect(rel_unique_key)
						end
						filter_obj = " WHERE #{rel_obj}.#{primary_rel_key} " <> key
						rel_label_obj = String.downcase(rel_label)
						rel_point_belong = case rel_mod.belong_to() do
							%{dir: rel_belong_dir, mod: rel_belong_mod, label: rel_belong_label} ->
								get_point(rel_belong_dir, "", rel_belong_label, "") <> " (#{belong_obj})"
							_ ->
								""
						end
						get = " OPTIONAL MATCH (#{target})" <>
						      get_point(rel_dir, rel_label_obj, rel_label, "")
						      <> "(#{rel_obj}:#{rel_mod.label()}) " <> rel_point_belong <> filter_obj
						{[get | get_acc], [rel_obj, rel_label_obj], [rel_label_obj | del_acc]}
				end
			end
		)
		return = get_returning(target_mod, return)
		"""
		#{[get_belong | get_acc]}
		#{with_(with1 ++ [target | with_acc])}
		#{where(filter)}
		#{delete(Enum.join(del_acc, ", "))}
		#{iif?(return == "", "", "RETURN #{return}")}
		"""
	end

	def delete_test() do
		#        delete("test_slab", LabNeoDb, %{unique_keys: ["1", "2"]})
	end

	# {uid => %{}}
	def delete(belong_uid, mod, struct) when is_map(struct) do
		{return, struct} = Map.pop(struct, :returning, %{})
		#        {with1, struct} = Map.pop(struct, :with, [])
		#        {filter, struct} = Map.pop(struct, :filter, [])
		{unique_keys, struct} = Map.pop(struct, :unique_keys, [])
		check_params_valid!(unique_keys)
		obj = mod.deepr_object()
		all_fields = mod.fields()
		%{mod: belong_mod, label: belong_label, dir: belong_dir} = mod.belong_to
		{primary_key, _} = mod.primary_key
		{belong_primary_key, _} = belong_mod.primary_key
		label = mod.label()
		has_visible = Map.get(all_fields, "visible") == nil
		{match_acc, with_acc, del_acc} = get_match_relation(mod)
		del_str = if has_visible do
			detach_delete(obj)
		else
			" SET #{obj}.visible = false " <> delete(Enum.join(del_acc, ", "))
		end
		return = get_returning(mod, return)
		belong_obj = belong_mod.deepr_object()
		match(obj, label) <> get_point(belong_dir, belong_label)
		<> object(belong_obj, belong_mod, belong_primary_key, belong_uid) <>
		   where(:in, obj, primary_key, unique_keys)
		   <> "#{match_acc}" <> with_([belong_obj, obj | with_acc])
		   <> del_str <> iif?(return == "", "", " RETURN #{return}")
	end
	def delete(belong_uid, mod, unique_keys) do
		delete(belong_uid, mod, pk_init(unique_keys))
	end

	@doc"""
	得到mod的所有关系对象
	"""
	def get_match_relation(target_mod) do
		target_obj = target_mod.deepr_object()
		target_mod.fields()
		|> Enum.reduce(
			   {[], [], []},
			   fn
				   ({k, %{dir: dir, mod: mod, label: label, type: type}}, {match_acc, with_acc, rel_acc})
				   when type != :belong_to ->
					   with_obj = if type == :one_to_many do
						   "COLLECT(#{k}) AS #{k}"
					   else
						   k
					   end
					   rel = String.downcase(label)
					   {
						   " OPTIONAL MATCH (#{target_obj})#{get_point(dir, label)} (#{k}:#{mod.label()})"
						   |> append2(match_acc),
						   [with_obj, rel | with_acc],
						   [rel | rel_acc]
					   }
				   (_, acc) ->
					   acc
			   end
		   )
	end

	@doc """
	匹配目标对象中 objs 的关系
	"""
	def match_target_relation(target_mod, objs) do
		all_fields = target_mod.fields()
		Enum.reduce(
			objs,
			{[], [], []},
			fn (obj, {op_acc, with_acc, do_rel_acc}) ->
				case Map.get(all_fields, obj) do
					%{dir: dir, mod: mod, label: label, type: type} ->
						with_obj = if type == :one_to_many do
							"COLLECT(#{obj}) AS #{obj}"
						else
							obj
						end
						rel = String.downcase(label)
						{
							" OPTIONAL MATCH (#{target_mod.deepr_object()})#{get_point(dir, label)}" <>
							"(#{obj}:#{mod.label()})"
							|> append2(op_acc),
							[with_obj, rel | with_acc],
							[rel | do_rel_acc]
						}
					_ ->
						PrivateCandy.remind(__ENV__)
				end
			end
		)
	end

	def get_returning(target_mod, return) do
		fields = target_mod.fields()
		Enum.map_join(
			return,
			", ",
			fn
				({obj, args}) when is_list(args) ->
					len = String.length(obj) - 1
					o = Enum.map_join(args, ", ", &(&1 <> ": " <> obj <> "." <> &1))
					case Map.get(fields, :binary.part(obj, 0, len)) do
						%{type: :one_to_many} ->
							"COLLECT({#{o}}) AS #{obj}"
						_ ->
							"{#{o}} AS #{obj}"
					end
				({obj, v}) ->
					len = String.length(obj) - 1
					case Map.get(fields, :binary.part(obj, 0, len)) do
						%{type: :one_to_many} ->
							"COLLECT(" <> v <> "." <> obj <> "}) AS " <> obj
						_ ->
							v <> "." <> obj <> " AS " <> obj
					end
				(v) ->
					case Map.get(fields, v) do
						nil ->
							v
						%{type: type} ->
							s = "PROPERTIES(#{v})"
							if type == :one_to_many do
								"COLLECT(#{s}) AS #{v}"
							else
								s <> " AS #{v}"
							end
					end
			end
		)
	end

	def write(method, belong_uid, unique_key, struct) do
		check_params_valid!(unique_key)
		{target_mod, struct} = Map.pop(struct, @struct_field)
		check_params_valid!(struct)
		target_label = target_mod.label()
		target = String.downcase(target_label)
		{return, struct} = Map.pop(struct, :returning, %{})
		{with1, struct} = Map.pop(struct, :with, [])
		{filter, struct} = Map.pop(struct, :filter, [])
		{optional, struct} = Map.pop(struct, :optional, [])
		{op_acc, with_acc, _rel_acc} = match_target_relation(target_mod, optional)
		{args, match, with, do_} =
			do_relation_cypher(method, target_mod, belong_uid, unique_key, target_label, struct)
		{args, belong_match, belong_with, where, create_belong} =
			case target_mod.belong_to() do
				%{dir: _} = rel_info ->
					belong_cypher(method, target_mod, belong_uid, unique_key, target, target_label, rel_info, args)
				_ ->
					{args, [], [], [], []}
			end
		return = get_returning(target_mod, return)
		"""
		#{op_acc ++ belong_match ++ match}
		#{with_(with_acc ++ with1 ++ belong_with ++ with)}
		#{where(filter ++ where)}
		#{create_belong}
		#{do_}
		#{iif?(return == "", "", "RETURN #{return}")}
		"""
	end

	@doc """
	args 中有关系对象的时候, 对关系对象的处理, 根据关系对象的id create relation
	设置值日人: %{"dutyer"=> [%{"人唯一id字段"=> id, "week_day" => day}},会拿到该 lab 的所有值日人,没有当前设置id的人会被删除
	    也即: optional match (p)<-[:ACCESS]-(dutyer)-[duty1:DUTY]->(lab) where dutyer.id not in ids delete duty1
	设置所属区域: %{"area" => area的唯一id字段}
	    optional match (p)<-[:BELONG]-(area)<-[at1:AT]-(lab) delete at1
	设置lab属性: %{"id" => id, "name" => name}
	    返回空"", 不在这里设置
	"""
	def do_relation_cypher(method, target_mod, belong_uid, unique_key, target_label, args) do
		target_label = String.downcase(target_label)
		all_fields = target_mod.fields()
		Enum.reduce(
			args,
			{args, [], [], []},
			fn ({key, value}, {args_acc, get_accs, with_accs, do_accs} = acc) ->
				# 修改 belong对象的属性
				case Map.get(all_fields, key) do
					%{type: :belong_to, mod: belong_mod} ->
						#                        PrivateCandy.debug_remind(__ENV__, "输入错误")
						belong_obj_primary_key = belong_mod.primary_key()
						                         |> elem(0)
						Enum.count(value) != 0 || throw({:error, "输入错误"})
						do_ = Enum.map_join(
							value,
							", ",
							fn
								({k, v}) ->
									belong_obj_primary_key != k || PrivateCandy.debug_remind(__ENV__, input_error())
									key <> "." <> k <> " = " <> v
								#                                (v) ->
								#                                    v
							end
						)
						{Map.delete(args_acc, key), get_accs, with_accs, ["SET #{do_} " | do_accs]}
					# 是关系对象,根据关系对象的id match,然后 with 对象出来, do 创建/删除关系
					%{mod: _mod} = rel_info ->
						{get_acc, with_acc, do_acc} = relation_cypher(
							method,
							target_mod,
							target_label,
							rel_info,
							key,
							value
						)
						{Map.delete(args_acc, key), get_acc ++ get_accs, with_acc ++ with_accs, do_acc ++ do_accs}
					%{} ->
						{args_acc, get_accs, with_accs, do_accs}
					nil ->
						PrivateCandy.remind(__ENV__)
				end
			end
		)
	end

	def old_obj(obj) do
		obj <> "1"
	end

	@doc """
	    查询 target 的关系对象
	"""
	defp relation_cypher(method, _target_mod, target, rel_info, obj, value) do
		%{mod: mod, label: rel_label, dir: rel_dir, type: rel_type} = rel_info
		%{:dir => dir, :mod => belong_mod, :label => belong_label} = mod.belong_to()
		belong_obj = belong_mod.deepr_object()
		obj_label = mod.label()
		{primary_key, is_unique} = mod.primary_key()
		dir_rel_str = get_point(dir, rel_label)
		rel_belong_str = get_point(dir, belong_label)
		delete_obj = obj <> "1"
		rel_obj = String.downcase(rel_label)
		del_rel_obj = rel_obj <> "1"
		dir_rel_del_str = get_point(rel_dir, del_rel_obj, rel_label, "")
		is_one_to_one = rel_type == :one_to_one
		is_one_to_many = rel_type == :one_to_many
		cond do
			method == :create_rel ->
				f = if is_list(value) do
					is_one_to_one && length(value) == 1 || is_one_to_many || PrivateCandy.remind(__ENV__)
					" IN "
				else
					" = "
				end
				create_rel_dir_str = get_point(dir, "", belong_label, "")
				filter = "WHERE #{obj}.#{primary_key} #{f} #{inspect(value)} "
				get_rel_obj = "OPTIONAL MATCH (#{obj}:#{obj_label})#{create_rel_dir_str}(#{belong_obj}) #{filter} "
				if is_one_to_one do
					create_rel = " MERGE (#{target})#{get_point(rel_dir, rel_label)}(#{obj}) "
					{get_delete_obj, with_del_boj, del_acc} =
						del_match = "OPTIONAL MATCH (#{target})#{dir_rel_del_str}
                        (#{delete_obj}:#{obj_label})#{create_rel_dir_str}(#{belong_obj})"
						<> " WHERE #{obj}.#{primary_key} NOT #{f} #{inspect(value)} "
					{[get_rel_obj, del_match], [del_rel_obj, obj, delete_obj], [create_rel, "DELETE #{del_rel_obj} "]}
				else
					coll = "COLLECT(#{obj}) AS #{obj}s "
					create_rel = " FOREACH(x IN #{obj}s | MERGE (#{target})#{get_point(rel_dir, rel_label)}(x))"
					{[get_rel_obj], [coll], [create_rel]}
				end
			# 关系是 one_to_one 要先删除之前存在的关系
			is_one_to_one ->
				!is_list(value) || PrivateCandy.remind(__ENV__)
				filter = "WHERE #{obj}.#{primary_key} = #{inspect(value)} "
				{get_delete_obj, with_del_boj, del_acc} = if method == :update do
					del_match = "OPTIONAL MATCH (#{target})#{dir_rel_del_str}
                        (#{delete_obj}:#{obj_label})#{rel_belong_str}(#{belong_obj}) "
					{[del_match], [del_rel_obj, delete_obj], ["DELETE #{del_rel_obj} "]}
				else
					{[], [], []}
				end
				get_rel_obj = "OPTIONAL MATCH (#{obj}:#{obj_label})#{rel_belong_str}(#{belong_obj}) #{filter} "
				create_rel = "MERGE (#{target})#{get_point(rel_dir, rel_label)}(#{obj}) "
				with = [obj | with_del_boj]
				{[get_rel_obj | get_delete_obj], with, [create_rel | del_acc]}
			is_one_to_many ->
				is_list(value) || PrivateCandy.remind(__ENV__)
				head = hd(value)
				# 是否是 map
				is_map = Kernel.is_map(head)
				arg_obj_on_rel = if is_map do
					# 如果参数有任何一个 key 在 relation 对象的keys 里, 则说明有值在 relation 上,是binary,则value是对象的主键
					MapLib.any_exists?(Map.keys(head), Map.keys(Map.get(rel_info, rel_obj)))
				else
					false
				end
				Enum.each(
					value,
					fn (h) ->
						!is_map || is_map && Kernel.is_map(h) ||
							PrivateCandy.debug_remind(__ENV__, "value的格式要一致, value:#{value}")
					end
				)
				unique_keys = if arg_obj_on_rel do
					MapLib.get_by_key(value, primary_key)
				else
					value
				end
				{get_delete_rel, with_delete_obj, delete_rel} = if method == :update do
					s = "OPTIONAL MATCH (#{target})#{dir_rel_del_str}(#{delete_obj}:#{obj_label}) "
					<> rel_belong_str <> "(#{belong_obj})"
					<> " WHERE NOT #{delete_obj}.#{primary_key} IN #{inspect(unique_keys)} "
					{[s], [del_rel_obj, delete_obj], ["DELETE #{del_rel_obj} "]}
				else
					{[], [], []}
				end
				if arg_obj_on_rel do
					rel_attr = "{" <> CypherLib.unwind_row_obj_args(value, "row", [primary_key]) <> "}"
					get_rel_obj = "UNWIND #{CypherLib.to_map_array(value)} AS row "
					<> "OPTIONAL MATCH (#{obj}:#{obj_label}{#{primary_key}: row.#{primary_key}}) " <>
					   "#{get_point(rel_dir, "", belong_label)}(#{belong_obj}) "
					create_rel = "MERGE (#{target})#{get_point(rel_dir, rel_label, rel_attr)}(#{obj}) "
					with = with_delete_obj ++ [obj, "row"]
					{[get_rel_obj | get_delete_rel], with, [create_rel | delete_rel]}
				else
					get_rel_obj = "OPTIONAL MATCH (#{obj}:#{obj_label})#{rel_belong_str}(#{belong_obj}) "
					<> "WHERE #{obj}.#{primary_key} IN #{inspect(value)} "
					create_rel = "MERGE (#{target})#{get_point(rel_dir, rel_label)}(#{obj}) "
					with = with_delete_obj ++ [obj]
					{[get_rel_obj | get_delete_rel], with, [create_rel | delete_rel]}
				end
			true ->
				throw({:error, {obj, value, rel_type}, input_error()})
		end
	end

	@doc """
	拼接 target 属于哪个项目的语句
	method是insert,update:
	    如果id不是唯一的,则会匹配目前新增或更新的对象id,检查存在与否(insert:存在不能插入;update:则根据是否更新了id来决定是否能更新)
	match (lab)-[:BELONG]->(p)
	insert, update 的时候如果id不唯一:
	    optional match (lab1:LAB{id: $unique_key})-[:BELONG]->(p)
	    insert: where not exists ((p)--(lab1)) merge (lab)-[:BELONG]->(p)
	    update: where unique_key = name OR not exists ((p)--(lab1)) set lab.name = $name...
	"""
	defp belong_cypher(method, target_mod, belong_uid, unique_key, target, target_label, rel_info, args) do
		%{dir: belong_dir, mod: belong_mod, label: belong_label} = rel_info
		{args, relation_attr} = relation_obj_cypher(belong_label, args)
		# match对象的belong对象
		{belong_primary_key, _} = belong_mod.primary_key()
		belong_node = belong_mod.label()
		belong_obj = String.downcase(belong_node)
		match_belong = match(belong_obj, belong_node, belong_primary_key, belong_uid)
		target1 = old_obj(target)
		{primary_key, is_unique} = target_mod.primary_key()
		primary_value = Map.get(args, primary_key)
		not_unique_exists_match = iif?(
			is_unique,
			"",
			optional_match_obj(target1, target_label, primary_key, primary_value)
			<> get_point(belong_dir, "", belong_label) <> object(belong_obj)
		)
		create_rel = get_point(belong_dir, belong_label, relation_attr) <> object(belong_obj)
		{match, with_acc, where_acc, create_obj} =
			cond do
				method == :insert ->
					create_rel = " MERGE " <> CypherLib.insert_cypher(target_mod, args) <> create_rel
					{
						match_belong <> not_unique_exists_match,
						iif?(is_unique, [], [target1]),
						iif?(is_unique, [], [not_exist(belong_obj, target1)]),
						[create_rel]
					}
				method == :update ->
					# 更新，match unique_key 的对象
					args = if is_unique do
						Map.delete(args, primary_key)
					else
						args
					end
					match_object = match_object = match(target, target_label, primary_key, unique_key)
					<> get_point(belong_dir, "", belong_label) <> object(belong_obj)
					update_obj = set_str(target, args)
					update_rel = if relation_attr != "" do
						" MERGE (#{target})" <> get_point(belong_dir, belong_label, relation_attr) <> "(#{belong_obj})"
						|> convert_list()
					else
						[]
					end
					{
						match_belong <> match_object <> not_unique_exists_match,
						iif?(is_unique, [target], [target, target1]),
						iif?(is_unique, [], [or_(primary_value == unique_key, not_exist(belong_obj, target1))]),
						[update_obj | update_rel]
					}
				method == :create_rel ->
					match_object = match(target, target_label, primary_key, unique_key)
					<> get_point(belong_dir, belong_label) <> object(belong_obj)
					{
						match_belong <> match_object,
						[target],
						[],
						[]
					}
			end
		{
			args,
			[match],
			[belong_obj | with_acc],
			where_acc,
			create_obj
		}
	end

	defp relation_obj_cypher(rel_name, args) do
		rel_name = String.downcase(rel_name)
		case Map.pop(args, rel_name) do
			{nil, args} ->
				{args, ""}
			{val, args} ->
				{args, CypherLib.kv_cypher(val)}
		end
	end

	def if_do(expr, for_do, ele \\ "r") do
		"""
		FOREACH (#{ele} IN CASE WHEN #{expr} THEN [1] ELSE [] END |
		    #{for_do}
		)
		"""
	end

	def set_str(obj, args) do
		" SET " <> Enum.map_join(args, ", ", &("#{obj}.#{elem(&1, 0)} = #{inspect(to_str(elem(&1, 1)))}"))
	end

	def to_str(v) do
		case Kernel.is_map(v) do
			true ->
				Jason.encode!(v)
			_ ->
				v
		end
	end

	# 把 elixir 里的[%{}] =>　neo4j 里的数组对象 [{k: v}]
	def to_map_array(enums) do
		"[" <> Enum.map_join(enums, ", ", &to_map_str(&1)) <> "]"
	end

	def to_map_str(enum) do
		str = enum
		      |> Enum.map_join(
			         ", ",
			         fn
				         ({field, value}) when is_map(value) ->
					         field <> ":" <> inspect(Jason.encode!(value))
				         ({field, value}) ->
					         field <> ":" <> inspect(value)
			         end
		         )
		"{" <> str <> "}"
	end


	def get_point(dir, rel_label) do
		get_point(dir, rel_label, "")
	end
	def get_point(dir, rel_label, rel_attr) do
		get_point(dir, String.downcase(rel_label), rel_label, rel_attr)
	end
	def get_point("->", rel_name, rel_label, rel_attr) do
		"-[#{rel_name}:#{rel_label}#{rel_attr}]->"
	end
	def get_point("<-", rel_name, rel_label, rel_attr) do
		"<-[#{rel_name}:#{rel_label}#{rel_attr}]-"
	end


	def foreach(obj, rel_obj, rel_name, relation_attr) do
		" FOREACH(r IN #{rel_obj}s | MERGE (#{obj})-[:#{rel_name}#{relation_attr}]->(r)) "
	end

	def where(:in, obj, field, value) do
		"WHERE #{obj}.#{field} IN #{inspect(value)} "
	end
	def where(:not_in, obj, field, value) do
		"WHERE NOT #{obj}.#{field} IN #{inspect(value)} "
	end

	def where("") do
		""
	end
	def where([]) do
		""
	end
	def where(values) do
		" WHERE " <> Enum.join(values, " AND ")
	end

	def match(obj) do
		"MATCH " <> object(obj)
	end
	def match(obj, label) do
		"MATCH " <> object(obj, label)
	end
	def match(obj, label, primary_key, key_value) do
		"MATCH " <> object(obj, label, primary_key, key_value)
	end


	def object(obj) do
		"(#{obj}) "
	end
	def object(obj, mod) when is_atom(mod) do
		"(#{obj}:#{mod.label()}) "
	end
	def object(obj, label) do
		"(#{obj}:#{label}) "
	end
	def object(obj, mod, primary_key, key_value) when is_atom(mod) do
		"(#{obj}:#{mod.label()}{#{primary_key}: #{inspect(CypherLib.to_str(key_value))}}) "
	end
	def object(obj, label, primary_key, key_value) do
		"(#{obj}:#{label}{#{primary_key}: #{inspect(CypherLib.to_str(key_value))}}) "
	end


	def collect(obj) do
		"COLLECT(#{obj}) AS #{obj}s"
	end

	def optional_match_obj(obj) do
		"OPTIONAL " <> match(obj)
	end
	def optional_match_obj(obj, label) do
		"OPTIONAL " <> match(obj, label)
	end
	def optional_match_obj(obj, label, primary_key, key_value) do
		"OPTIONAL " <> match(obj, label, primary_key, key_value)
	end


	def exists(obj1, obj2) do
		"EXISTS((#{obj1})--(#{obj2}))"
	end

	def not_exist(obj1, obj2) do
		"NOT " <> exists(obj1, obj2)
	end

	defp delete(obj) do
		" DELETE #{obj}"
	end
	def detach_delete(obj) do
		" DETACH " <> delete(obj)
	end

	def or_(str1, str2) do
		"(#{str1}" <> " OR " <> str2 <> ") "
	end

	def with_([]) do
		""
	end
	def with_(with) do
		" WITH " <> Enum.join(with, ", ")
	end

	def merge(mod, args, create) do
		" MERGE " <> CypherLib.insert_cypher(mod, args) <> create
	end

	def convert(v) when is_list(v) do
		inspect(v)
	end
	def convert(v) when is_map(v) do
		inspect(Jason.encode!(v))
	end
	def convert(v) when is_binary(v) do
		inspect(v)
	end
	def convert(v) when is_struct(v) do
		inspect(v)
	end
	def convert(v) do
		v
	end

	def test_get() do
		select = %{
			"employee" => [
				"uid",
				"name",
				"phone",
				"number",
				"gender",
				"birthday"
			],
			#            "technique" => ["name", "items"],
			#            "work_for" => ["roles"]
		}
		select = %{"pmgt_role" => ["name"]}
		project_id = "dfd3dc14881e437086a9e721ef4b8dcd"
		#        Neo4jRepo.get!(project_id, select, [], [], [])

	end

	def properties(obj) do
		" PROPERTIES(#{obj}) AS #{obj}"
	end

	# return: list
	def put_returning(args, return) do
		Map.put(args, :returning, return)
	end

	def init(args, return, with \\ [], optional \\ [], filter \\ []) do
		Map.put(args, :returning, return)
		|> Map.put(:with, with)
		|> Map.put(:optional, with)
		|> Map.put(:filter, filter)
	end

	def pk_init(unique_keys, return \\ [], with \\ [], optional \\ [], filter \\ []) do
		%{unique_key: unique_keys}
		|> init(return, with, filter)
	end

	@doc """
	    防cypher注入
	"""
	def check_params_valid!(map) when is_map(map) or is_struct(map) do
		Enum.each(
			map,
			fn ({k, v}) ->
				check_params_valid!(k)
				check_params_valid!(v)
			end
		)
	end
	def check_params_valid!(list) when is_list(list) do
		Enum.each(list, &(check_params_valid!(&1)))
	end
	def check_params_valid!(val) do
		is_binary(val) &&
			String.contains?(
				val,
				[
					"$",
					"//",
					"'",
					" match ",
					" MATCH ",
					" delete ",
					" DELETE ",
					" create ",
					"  CREATE ",
					" MERGE ",
					" merge ",
					" SET ",
					" set ",
					" or ",
					" OR "
				]
			)
		&& PrivateCandy.debug_remind(__ENV__, "输入错误")
	end


end
